home *** CD-ROM | disk | FTP | other *** search
-
- ==================
- = ARexx and ISAM =
- ==================
-
- Except for the addition of two record-handling functions, described later,
- rexxisam.library corresponds almost exactly to isam.library, as the ARexx
- functions actually call the functions in isam.library. Therefore, ARexx
- users should still refer to the AutoDocs file as well as the main documen-
- tation file as your principle source of information.
-
- That having been said, there are a few differences in calling the various
- functions.
-
- - The most obvious difference is the presence of the new function EndISAM.
- EndISAM is used by rexxisam.library as a signal that you no longer need
- isam.library. isam.library is therefore closed. isam.library was auto-
- matically opened when your first ISAM function was called.
-
- The reason rexxisam.library is necessary, and its functions not just
- added to isam.library, is that when ARexx calls a function library,
- it opens the library, calls the function, and then closes the library.
-
- Since closing isam.library indicates that you no longer need to use ISAM,
- at that point, any ISAM files that are still open are closed automat-
- ically.
-
- So, supposing that you call OpenISAMFile: ARexx opens isam.library and
- calls OpenISAMFIle, ISAM opens your file for you, ARexx closes isam.
- library, ISAM closes your file again. Oops.
-
- We get around this problem by using rexx.library. This library is, of
- course, opened/closed just as isam.library was in our example above.
- But opening rexx.library doesn't open isam.library, and closing rexx.
- library doesn't close isam.library.
- rexx.library has been designed to look for isam.library being open
- whenever it is asked to call an ISAM function. If it is open, it calls
- the function. If not, it opens the library and then calls the function.
- Since the closing of isam.library is not automatic, it is necessary to
- explicitly tell rexx.library to close it. This is done by calling
- EndISAM. EndISAM takes no parameters.
-
- - the next most obvious difference is that ISAMWhy now has two parameters.
- ISAMWhy ordinarily has one parameter (the error number) and returns
- a string containing the text of the error message. From ARexx, this
- has been changed so that the ISAMWhy function now returns in the same
- manner as every other function - it returns a numerical value that
- is zero if the function succeeds, and non-zero otherwise. The second
- parameter is now the error message text.
-
- - note that in calling ISAM functions (or, indeed, ANY functions in ARexx),
- there must be NO SPACE between the end of the function name, and the
- opening parenthesis.
-
- Ex. CloseISAMFile( ISAMHandle ) is OK, but...
- CloseISAMFile ( ISAMHandle ) will cause an ARexx error.
-
- - note also, that since there are often quite a few parameters in ISAM
- functions, that if the function call is divided between two or more
- lines, that an extra comma is needed at the end of all but the last
- line (as one normally divides the line between parameters), because
- ARexx will otherwise interpret the parameter-dividing comma as a
- continuation comma, and will concatenate the parameter with the first
- parameter on the next line.
-
- Ex. error = OpenISAMFile( "SPECS:employee.specs", 1, <---WRONG
- 'R', 0, "IH" )
- error = OpenISAMFile( "SPECS:employee.specs", 1, , <---RIGHT
- 'R', 0, "IH" )
-
- - ARexx deals with alphanumeric text strings only - numerical data that
- a C or assembler programmer would often use in keys and records (such
- as UBYTE, WORD, DOUBLE, etc. ) cannot be directly handled in ARexx.
-
- Also, ISAM often needs to know an ADDRESS of an area of memory where
- a record or returned value is to be stored, and ARexx generally doesn't
- deal with addresses, except in memory "getspace"d or "allocmem"ed.
- This necessitated two alterations:
-
- - records exist only in memory obtained by the getspace or allocmem
- calls, and are not directly handled by the ARexx user. Two func-
- tions, AssembleRecord and DisAssemble record, take as parameters
- the variable holding the address of the record, a string denoting
- the types and lengths of the record fields, and a string containing
- the names of the variables that will/do contain strings representing
- the record fields. (This is simpler than it sounds).
-
- key values being sent to (or returned from) ISAM must also be
- held in allocated memory, so that their address may be provided.
- They are manipulated in the same manner, with the same functions.
-
- Memory allocated with allocmem() can have several attributes, most of
- which may be combined. These attributes are specified as the
- (optional) second parameter. The attributes are specified as a
- four-byte string. The attributes and strings are as follows:
-
- PUBLIC '0000 0001'x
- CHIP '0000 0002'x
- FAST '0000 0004'x
- CLEAR '0001 0000'x
-
- If the attribute CLEAR is specified (alone or in combination with
- another attribute), then the memory is cleared (or turned to all
- zeros). It is often a good idea to specify CLEAR, as otherwise,
- the memory could contain anything, and usually nothing useful.
-
- Attributes may be combined by specifying the addition of two or
- more attributes (visually - ARexx won't add this type of variable)
- or using the bitwise-OR function. It may be convenient to set
- variables to the attribute values first.
- Note that memory cannot be both FAST and CHIP.
-
- Ex.
-
- attributes = '0001 0003'x /* combines PUBLIC, CHIP and CLEAR */
- memory = allocmem( 25, attributes )
-
- attributes = BITOR( FAST, CLEAR )
- attributes = BITOR( attributes, PUBLIC) /* FAST/CLEAR/PUBLIC
- memory = allocmem( 25, attributes ) having been
- previously set. */
-
- attributes = BITOR( BITOR( FAST, CLEAR ), PUBLIC ) /* '' */
-
- attribute = CLEAR
- memory = allocmem( 25, attribute ) /* gimme 25 bytes,
- and clear 'em first */
-
-
- - functions returning numeric data (such as the record number or number
- of counted records) must provide as that parameter a STRING containing
- the NAME of the variable in which to store the data.
-
- Ex. error = OpenISAMFile( "SPECS:employee.specs", 1, 'R', 0, "IH" )
-
- if this function returns successfully (error = 0), the variable
- IH will have been set to the ISAMHandle of the file being opened.
- IH would then be provided to the various other functions that
- require an ISAMHandle as a parameter, with NO QUOTES
-
- Ex. error = CloseISAMFile( IH )
-
- because, in this case, the ISAMHandle is being provided TO ISAM,
- not returned BY ISAM.
-
- The function ISAMWhy, as mentioned above, also uses this technique
- to return the error message text.
- Ex.
- error = ISAMWhy( 35, "Message" )
-
- The variable MESSAGE will contain the text of message 35, or an
- empty string (if 35 is not a valid ISAM error number).
-
-
- - any ISAM function that requires a numeric parameter (ULONG, WORD, etc.)
- need only provide a normal ARexx string containing the required value.
-
- Parameters of BOOL type (C/assembler programmers know the two possible
- values as TRUE and FALSE) require only a normal ARexx string contain-
- ing the value 1 or 0. Probably the best way (most readable) to accom-
- plish this is to make two variables TRUE and FALSE, set them to 1 and 0,
- respectively, and then use the variables in your function calls:
-
- Ex.
-
- TRUE = 1; FALSE = 0
-
- error = OpenISAMFile( "SPECS:employee.specs", TRUE, "R", FALSE, "IH" )
-
-
- - As detailed in the main documentation, ISAM functions return zero for
- success, and a number larger than zero (an error number) for failure.
- It helpful to define a variable "OK" (or some such name) that, like TRUE
- and FALSE above, make the program more readable.
- Set the value for success to zero.
-
- Ex.
-
- OK = 0
-
-
- - any ISAM function that requires a parameter of type CHAR (LockType)
- should provide a normal ARexx string with the desired value in the first
- character position. Anything beyond the first character will be ignored.
-
- - any memory containing record/key data allocated with getspace() or alloc-
- mem() should free the memory (with freespace()/freemem(), respectively)
- before the program ends. While it is true that memory allocated with
- getspace() will automatically be freed at program end, it is always good
- programming practice to explicitly free memory you have allocated.
- Memory allocated with allocmem() MUST be explicitly freed using freemem(),
- or it will be lost until the next reboot.
-
-
- - as with any function library (like the support library rexxsupport.library)
- you must add rexxisam.library to ARexx's list of libraries, before any
- functions in it can be called. You may also want to remove the library
- from the list when your program has finished using ISAM (probably at the
- end of the program).
- (For this to work, rexxisam.library must have been placed in the LIBS:
- assigned directory.)
-
- Ex.
- if ( ~show( 'L', 'rexxisam.library' ) )
- then
- do
- if ( ~addlib( 'rexxisam.library', 0, -30, 0 ) )
- then
- do
- say "Couldn't open rexxisam library."
- exit 10
- end
- end
-
- ...
-
- if ( show( 'L', 'rexxisam.library' ) ) then
- remlib( 'rexxisam.library' )
-
-
- - The example program books.c has been re-written in ARexx, with some
- small modification. Take a look at the code to get a better idea how
- to incorporate ISAM into your programs, and appropriate whatever you
- like for your own use.
-
-
-
- ---------------------
- - THE NEW FUNCTIONS -
- ---------------------
-
- AssembleRecord( Record, RecLen, Types, VarNames )
-
- - takes a string of types and a string of variable names and creates a
- record of data with the indicated types at the indicated address, and
- limits the record to the indicated length.
-
- Ex.
- AssembleRecord( RecAddr, 32, "s20 u4 f8", "Name NumYears Salary" )
-
- takes the first 20 characters of the string contained in the variable
- Name, the numerical variable NumYears expressed as a four-byte un-
- signed integer (ULONG), and the numerical variable Salary expressed
- as an eight-byte floating point (DOUBLE), and places them one directly
- after each other at the address RecAddr. If the various type lengths
- exceeded 32 (in this case), an error would have been returned.
-
- Note that it does what you say, whether it is wrong or not: using this
- example, suppose Name contained the string "five", which is not 20
- characters long. Wherever Name is stored, the letters f i v e will
- be transferred to RecAddr, along with the next 16 bytes, which are
- NOT part of Name, and could be part of some other variable, or some-
- thing else entirely... If AssembleRecord is expecting a numeric
- string, and gets "fred" instead, the number zero may be stored, or
- maybe something else.
- Try to make sure strings contain the proper data.
- Text strings can be expanded to the correct length with LEFT(),
- RIGHT(), and other functions, and variables may be checked for
- containing numeric data with DATATYPE( string, <option> ), where
- <option> is "numeric" (any number) or "whole" (integer number).
-
-
- types interpretation C equivilent(s) permissible lengths
- ----- -------------- ------------------ -------------------
- s string CHAR, CHAR[]/UBYTE[] 1 or larger
- u unsigned integer UBYTE/UWORD/ULONG 1, 2, 4
- i signed integer BYTE/WORD/LONG/int 1, 2, 4
- f floating point FLOAT/DOUBLE 4, 8
-
- A particular variable name may be replaced by "*" if you do not wish
- to set/change that field. Note that if a record is assembled for the
- first time, and * is specified for a field, and getspace() was used
- (or allocmem(), without specifying CLEAR), then there is no way to
- predict what what value that field will contain. If allocmem() with
- CLEAR is specified, then the memory will be set to all zeros, and
- fields not set will remain zero.
-
-
- Possible errors:
-
- ERROR_INVALID_RECORD_LENGTH a negative length was specified
- ERROR_INVALID_KEY_TYPE type wasn't s, u, i, or f
- ERROR_INVALID_KEY_LENGTH length was negative, or wasn't
- valid for the type, or would result
- in the record length being exceeded
-
-
- DisAssembleRecord( Record, RecLen, Types, VarNames [,Formats] )
-
- - takes the same parameters as AssembleRecord, with the addition of the
- optional Formats string. Also like AssembleRecord, a variable name
- may be replaced with "*" if you don't care what the field contains and
- don't wish to set a variable to its value.
-
- Ex.
- types = "f8 f4 s3 s30"
- varnames = "score avg nickname name"
- error = DisAssembleRecord( RecAddr, 45, types, varnames)
-
- sets the variable SCORE to the floating point value stored in the
- first 8 bytes, sets AVG to the floating point value stored in the
- next four bytes, sets NICKNAME to a string formed by adding a null
- character to the next three bytes, and sets NAME to a string formed
- by adding a null character to the last 30 bytes. (These null char-
- acters are added to a copy of the record field - the record itself
- is unaltered).
-
- As mentioned above, another parameter may be entered after the string
- containing the variable names.
- This parameter contains C-language "printf"-style formatting strings.
-
- Numerical data is automatically formatted by a minimal printf-style
- format, as appropriate to the particular type:
- Ex.
- type 'u' is formatted using "%u"
- type 'i' "%d"
- type 'f' "%f"
- type 's' "%xs" (where x is the stated length)
-
- but you may substitute another format for the given one for each
- field by adding the formatting string parameter.
-
- Ex.
- types = "f8 f4 s3 s30"
- varnames = "score avg nickname name"
- formats = ' %20.7f "Average is: %10.3g" %10s "%40s is his name." '
- error = DisAssembleRecord( RecAddr, 45, types, varnames, formats)
-
- Note that, as in the example above (for the variables avg and name), if
- anything but the number itself occurs in a format (spaces, or explan-
- atory text) the format will have to be enclosed in DOUBLE QUOTES,
- which therefore means that the whole formatting string will need to be
- enclosed in SINGLE QUOTES. (The space after the first single quote,
- and the other space before the final single quote are present just to
- draw attention to the single quotes. They are unnecessary, and do not
- affect the output.)
-
-
- (If you don't understand this formatting stuff, just ignore it and
- leave out the formatting parameter. The fields will be automatically
- formatted. You may then change the way the resultant variable strings
- look with the various ARexx internal string-altering commands/functions
- after you call DisAssembleRecord.)
-
-
-
-
- --------------------------------
- - NOTES ON THE OTHER FUNCTIONS -
- --------------------------------
-
- CountISAMRecords( ISAMHandle, KeyNo, CountMax, Count )
- - remember to enclose the name of the variable to contain the record
- count in quotes.
-
- CloseISAMFile( ISAMHandle )
-
- CreateISAMFile( SpecsFileName )
-
- DeleteISAMFile( SpecsFileName )
-
- DeleteISAMRecord( ISAMHandle, RecNo )
-
- DeleteISAMRecord( ISAMHandle, KeyNo, Count )
- - remember to enclose the name of the variable to contain the deleted-
- record count in quotes.
-
- GetFirstLastISAMKeyValues( ISAMHandle, KeyNo, FKeyValue, FRecNo,
- LKeyValue, LRecNo )
- - remember to enclose the name of the variables to contain the record
- numbers (FRecNo/LRecNo) in quotes.
-
- - remember that FKeyValue/LKeyValue must be address variables (variables
- that contain an address obtained from the ARexx function getspace() or
- the rexxsupport.library function allocmem() ).
-
- ISAMWhy( ErrNo, ErrText )
- - remember to enclose the name of the variable to contain the error
- text in quotes.
-
- LockISAMFile( ISAMHandle, LockType )
-
- LockISAMRecord( ISAMHandle, RecNo, LockType )
-
- ModifyISAMRecord( ISAMHandle, RecNo, Record )
- - remember that Record must be an address variable (see GetFirst...).
-
- OpenISAMFile( SpecsFileName, LLock, LockType, SaveHead, ISAMHandle )
- - remember to enclose the name of the variable to contain the ISAM
- Handle in quotes.
-
- - remember that LLock and SaveHead need to be strings containing the
- values 1 or 0 (corresponding, respectively, to TRUE or FALSE).
-
- ReadISAMRecord( ISAMHandle, RecNO, LLock, LockType, Record )
- - remember that Record must be an address variable (see GetFirst...).
-
- - remember that LLock needs to be a string containing the value 1 or 0
- (corresponding, respectively, to TRUE or FALSE).
-
- ReadUniqueISAMRecord( ISAMHandle, KeyNo, KeyValue, LLock,
- LockType, RecNo, Record )
- - remember that KeyValue/Record must be address variables (see GetFirst...).
-
- - remember that LLock needs to be a string containing the value 1 or 0
- (corresponding, respectively, to TRUE or FALSE).
-
- - remember to enclose the name of the variable to contain the record
- number in quotes.
-
- ReadNextISAMKey( ISAMHandle, KeyNo, RecNo, KeyValue )
- - remember that KeyValue must be an address variable (see GetFirst...).
-
- - remember to enclose the name of the variable to contain the record
- number in quotes.
-
- ReadNextISAMRecord( ISAMHandle, KeyNo, LLock, Locktype, RecNo, Record )
- - remember that LLock needs to be a string containing the value 1 or 0
- (corresponding, respectively, to TRUE or FALSE).
-
- - remember to enclose the name of the variable to contain the record
- number in quotes.
-
- - remember that Record must be an address variable (see GetFirst...).
-
- ReIndexISAMFile( SpecsFileName, Counter )
- - remember that Counter needs to be a string containing the value 1 or 0
- (corresponding, respectively, to TRUE or FALSE).
-
- - Counter is an optional parameter.
-
- ReportISAMStatus()
-
- SetUpISAMIterationRange( ISAMHandle, KeyNo, IterType, KeyFrom, KeyTo )
- - remember that KeyFrom/KeyTo must be address variables (see GetFirst...).
-
- - In the AutoDocs, it mentions that, for certain values of IterType,
- KeyFrom/KeyTo "should be NULL". This may be accomplished in three ways:
-
- - Parameters may be omitted.
- If KeyTo should be NULL, its parameter may be omitted entirely.
- If KeyTo AND KeyFrom should be NULL, BOTH parameters may be omitted.
- Ex.
- (IH, KN, 1, KF ) ---> KeyTo parm is omitted.
- (IH, KN, 0 ) ---> both KeyFrom/KeyTo parms omitted.
- This method will not work if KeyTo is necessary, but KeyFrom is not.
-
- - "empty" parameters may be used (nothing between/after the commas):
- Ex.
- (IH, KN, IT, KF, ) ---> KeyTo parm is empty.
- (IH, KN, IT, , KT ) ---> KeyFrom parm is empty.
- (IH, KN, IT, , ) ---> KeyFrom/KeyTo parms both empty.
- Note that the spaces between/after the commas are not necessary,
- and may be left out.
-
- - Either/both NULL parameters may be specified by using the value
- zero (a string consisting of just the character "0" ).
- Ex.
- (IH, KN, 1, KF, 0 ) ---> KeyTo parm is zero.
- (IH, KN, 2, 0, KT ) ---> KeyFrom parm is zero.
- (IH, KN, 0, 0, 0 ) ---> both KeyFrom/KeyTo parms are zero.
- This is the preferred method.
-
-
- SetUpISAMIterationKey( ISAMHandle, KeyNo, KeyValue )
- - remember that KeyValue must be an address variable (see GetFirst...).
-
- SetUpISAMIterationPrefix( ISAMHandle, KeyNo, Prefix, Len )
- - remember that Prefix must be an address variable (see GetFirst...).
-
- ShutDownISAM()
-
- StoreISAMRecord( ISAMHandle, Record, LLock, LockType, RecNo )
- - remember that Record must be an address variable (see GetFirst...).
-
- - remember that LLock needs to be a string containing the value 1 or 0
- (corresponding, respectively, to TRUE or FALSE).
-
- - remember to enclose the name of the variable to contain the record
- number in quotes.
-
- UnLockISAMFile( ISAMHandle )
-
- UnLockISAMRecord( ISAMHandle, RecNo )
-
- UnLockAllISAMRecords( ISAMHandle )
-
-
-
-
- -------------------------------------------------------------
- - CREATING RECORDS/KEYS, AND MAKING THE SPECIFICATIONS FILE -
- -------------------------------------------------------------
-
- The ISAM Specifications (Specs) File needs to be created to hold information
- about your data. It contains a line holding the name of the data file that
- will be created, another line with the name of the index file to be created,
- a line with the record length, and one or more lines of key information.
-
- ISAM needs to know what information in the record is important to you,
- important enough to keep in sorted order in the index file. This is the key
- information. There might be only one key, or there might be many keys.
-
- Each key may be on a different part of the record, or maybe two keys will
- be on the same field(s) (one being sorted smallest-to-largest (ascending),
- and the other largest-to-smallest (descending) ). Maybe one key is on the
- combination of an ID field and the 1st 10 characters of the LastName, and
- another key on the full LastName.
-
- ISAM needs to know both where to find the key in the record, and just how
- large the key is. Where it is, is called the key's offset, and is counted
- starting from the beginning of the record. If the key starts at the begin-
- ning of the record its offset is 0 (zero)
-
- Let's have an example record (let's say, an employee):
-
- types = "u2 s30 f8 s9"
- names = "BranchID Name Salary SSN"
-
- (Where BranchID is the code for the particular branch of the company, and
- SSN is the Social Security Number, without the dashes).
-
- which we will use when we want to be able to call
-
- AssembleRecord( RecAddr, , types, names )
-
-
- First, let's establish the record length. (We will assume you will be using
- ARexx exclusively with this record, and hence won't need to deal with pad-
- bytes (see ISAM.doc) )
-
- Well, we see that we have 4 fields, whose lengths are: 2 ("u2"), 30 ("s30"),
- 8 ("f8"), and 9 ("s9"). Adding: 2+30+8+9 = 49. So, now we can say:
-
- AssembleRecord( RecAddr, 49, types, names )
-
- Now, let's make a table of the fields and their offsets and lengths, so that
- determining keys will be easier:
-
- FIELD OFFSET LENGTH
- -----------------------------
- BranchID 0 2
- Name 2 (=0+2) 30
- Salary 32 (=2+30) 8
- SSN 40 (=32+8) 9
- -------------
- 49 (=40+9)
-
- Note that the offset for a field is the offset for the previous field plus
- the length of the previous field. Note also, that if there were another
- field after the others, its offset would be the current record length.
- This is a good way to check your math for record length: the last offset plus
- the last length better equal the record length.
-
- OK. Now we know where and how large the fields are. Now, let's decide
- what keys we want.
-
- Well, we probably want to be able to find the employee by name, so let's
- make that the first key: offset = 2. The full name is pretty long.
- Surely we don't want to waste disk space indexing the full name, so let's
- limit it to 10. Length = 10. We'll assume that the last name will be
- first, followed by first (and middle?). It'll be a Text ("T") type, (so
- it'll always be in alphabetical order, regardless of case) and we'll prob-
- ably want it to be ascending, so we'll get the A's first. Finally, it ought
- to be a repeatable key ("R"), so that if John Smith is hired and you already
- have a John Smith working for you, it won't reject the second one (you don't
- want to have to tell the higher-ups that you couldn't hire John Smith because
- you already HAD one, do you?) This, the first key, will be referred to as
- key #0.
-
- OK. Next, the Social Security Number. The government will want to track
- your employee's taxes, and they do everything by number... Offset = 40,
- Length = 9. It doesn't matter if the key is of type Ascii ("A") or Text,
- as there will be only numbers in this field/key and numbers don't have a
- case. Just for the fun of it, let's make this one Descending ("D"). Social
- Security Numbers are unique to each person, so we'll make this a Unique
- ("U") key. Key #1.
-
- Finally, we'd like to be able to list the employees by branch office, so a
- key on BranchID would seem to be in order. However, if we just make a key
- on that one field, when we list all employees for one branch, the employees
- for that branch will be listed randomly (probably in an order similar to
- that in which their records were first entered into the system). We can
- fix that: extend the key to cover the BranchID AND the name field. Once
- again, let's limit the amount of the Name used to 10 characters. So:
- Offset = 0, Length = 12 (2 from BranchID + the first 10 of Name). The Type
- will have to be Ascii ("A"). Why? If it is type Text, then it is case-
- sensitive, and will be case sensitive along its whole length, not just the
- Name portion. Thus if one of the two bytes in the BranchID numeric field
- happens to correspond to an upper- or lower-case letter, it will be stored
- together with those of the other case - ex: (using just one byte) if the
- two BranchID's are 82 and 114, they will be listed together, even though
- 82 and 114 are way apart numerically, because they correspond to the upper-
- and lower-case "R". Using type "A" will fix that problem. It will also
- give a slightly different order to the names themselves than it would
- under type "T", that the earlier Name key used. (If this were an actual
- assignment, you might reconsider your choice of type "T" earlier for Key #0,
- just for the sake of consistancy. Or you might not.) We'll want Ascending
- ("A") again, so we'll still get the names in A-Z order. Once again, we'll
- want to allow keys to repeat (so more than one Jane Doe can work at the same
- branch) ("R"). Last (Third) Key: Key #2.
-
- So, we may now make the Specs File (remembering that the key lines have
- their info in this order: offset, length, type, Ascending/Descending,
- Unique/Repeatable):
-
- ----------------------------------------------------
- BR32:Employee.data
- BR32:Employee.index
- 49
- 2 10 T A R
- 40 9 A D U
- 0 12 A A R
- ----------------------------------------------------
- Where BR32 is an Assign'd logical device name.
-
- If we called the specs file Employee.specs, and placed it in the same loca-
- tion as the data/index files, we would then call the CreateISAMFile command
- with "BR32:Employee.specs" as the command argument. The Employee ISAM file
- would then be ready to use.
-
-
-